<!DOCTYPE html>

<html>

<head>
    <title>Open IDE</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link href="/static/icon/av.svg" rel="icon" type="image/x-icon">
    <style>
        body {
            margin: 0px;
            padding: 0px;
        }
        
        .dsl-main {
            position: absolute;
            top: 5px;
            bottom: 5px;
            left: 3px;
            right: 5px;
        }
        
        .inline {
            display: inline-block;
        }
        
        .flex {
            display: flex;
            flex-direction: column;
            height: 100%;
            width: 100%;
        }
        
        .editor-header {
            text-align: center;
            font-size: 1.3rem;
            font-weight: bolder;
        }
        
        .editor {
            width: 100%;
            border: 1px solid grey;
            flex-grow: 1;
        }
        
        .button {
            text-align: center;
            margin: 10px;
            margin-right: 20px;
            height: 25px;
        }
        
        .result {
            border: 1px;
            border-style: solid;
            height: 50px;
            padding-left: 5px;
            line-height: 25px;
            overflow: scroll;
        }
        
        button {
            margin: 0px 8px;
            font-size: 1.0rem;
        }
        
        pre {
            word-wrap: break-word;
        }
        
        #dslCodeInner {
            height: 1000px;
            width: 99.8%;
        }
    </style>

    <script>
        /**
         * 调试输出：支持alert和console两种输出方式
         */
        function debugOutput(message) {
            if (window.location.href.indexOf("&debug=true") > -1) {
                if (window.location.href.indexOf("&debugMode=alert") > -1) {
                    alert(message);
                } else {
                    console.info(message);
                }
            }
        }
    </script>

    <script src="https://7072-prd-ffx26-1255301037.tcb.qcloud.la/321zou.com/dsl/jquery/jquery-3.5.1.min.js?v=238619"></script>
    <script src="https://7072-prd-ffx26-1255301037.tcb.qcloud.la/321zou.com/dsl/monaco-editor/0.32.1/min/vs/loader.js"></script>

    <script src="/runtime/javascript/OpenDSLRuntime.js"></script>
    <script src="/parser/javascript/GrammarParser.js"></script>
</head>

<body>

    <div class="dsl-main">
        <div class="flex">

            <div class="editor-header"><span id="titleText"></span></div>

            <div id="notSupportedError" style='display: none;color:red'>browser not support rich-editor, change to other browser, like chrome!</div>

            <div id="dslCode" class="editor inline"></div>
            <div id="vimStatus" style="display: none;"></div>

            <div id="tool">
                <div class="button">
                    <button id="executeBtn" onclick="execute()">执行(Execute)</button>
                    <button id="toggleVimModeBtn" onclick="toggleVimMode()">切换VIM模式</button>
                    <button id="formatCodeBtn" onclick="formatCode()">格式化</button>
                </div>
                <div class="result"><span id="result"></span></div>
            </div>
        </div>
    </div>

    <script>
        function parseUrl() {
            var url = window.location.href;
            var query = url.split("?")[1];

            var param = {};
            if (query) {
                var parts = query.split("&");
                for (var i = 0; i < parts.length; i++) {
                    var keyValue = parts[i].split("=");
                    param[keyValue[0]] = keyValue[1];
                }
            }
            return param;
        }

        var urlParam = parseUrl();

        debugOutput("urlParam:" + JSON.stringify(urlParam));

        if (urlParam.title) {
            $("#titleText").text(decodeURI(urlParam.title));
            document.title = decodeURI(urlParam.title);
            if (urlParam.titleMargin) {
                $("#titleText").parent().css({
                    margin: urlParam.titleMargin - 0
                });
            }
        }
        if (urlParam.showTool) {
            $("#tool").show();
            if (urlParam.resultHeight) {
                $(".result").css({
                    height: urlParam.resultHeight - 0
                });
            }
        } else {
            $("#tool").hide();
        }
        debugOutput("urlParam.showTool:-end");
        var dslCodeEditor = null;
        var grammarParser = null;

        var code = "";
        var codeUrl = urlParam.code;

        var MonacoVim = null;

        function getIndentSpace(text) {
            var space = text.replace(/(\s*).+/, "$1");
            return space;
        }

        function isAtBracket(text) {
            return "{" === text.charAt(text.length - 1);
        }

        debugOutput("setTimeout-begin");

        setTimeout(() => {

            if (typeof(monaco) === "undefined") {
                debugOutput("codeUrl:" + codeUrl);
                $.get(codeUrl, function(dslCode) {
                    if (typeof(monaco) === "undefined") {
                        debugOutput("dslCode:" + dslCode);
                        code = dslCode;
                        var html = "<textarea id='dslCodeInner'>" + dslCode + "</textarea>";
                        $("#dslCode").html(html);
                        setTimeout(() => {
                            if (typeof(monaco) === "undefined") {
                                $("#notSupportedError").show();
                            }
                        }, 1000);
                    }
                })
            }
        }, 500);

        for (var _i = 1; _i < 51; _i++) {
            setTimeout(() => {
                if (typeof(monaco) !== "undefined") {
                    $("#notSupportedError").hide();
                }
            }, 500 * _i);
        }
        debugOutput("setTimeout-end");

        function getCode() {
            if (dslCodeEditor) {

                return dslCodeEditor.getValue();
            }
            return $("#dslCodeInner").val();
        }

        function setCode(code) {
            if (dslCodeEditor) {
                dslCodeEditor.setValue(code);
            } else {
                $("#dslCodeInner").val(code);
            }
        }

        function parseAST() {
            parseASTAndDo(function(result) {
                var astJsonText = result.data;
                if (!astJsonText) {
                    alert("解析AST错误: " + result.message);
                    return;
                }
                astEditor.setValue(JSON.stringify(JSON.parse(astJsonText).ast));
                astEditor.getAction('editor.action.formatDocument').run();
            })
        }
        debugOutput("dslExecuteMap-begin");
        var dslExecuteMap = {
            aviator: function(callback) {
                var code = getCode();
                $.ajax({
                    type: "POST",
                    url: "/execute/aviator",
                    contentType: "application/json", //必须有
                    dataType: "json", //表示返回值类型，不必须
                    data: JSON.stringify({
                        'code': code
                    }),
                    success: function(jsonResult) {
                        if (callback) {
                            callback(jsonResult);
                            return;
                        }
                        var resultText = "";
                        if (jsonResult.error) {
                            resultText = "<span style='color:red'>" + jsonResult.error + "</span>";
                        } else {
                            if (jsonResult.print) {
                                resultText += jsonResult.print.replace(/\n/g, "<br/>");
                                resultText += "<br/>";
                            }
                            if (jsonResult.output) {
                                resultText += formatValue(jsonResult.output);
                            }
                        }
                        $("#result").html(resultText);
                    }
                });
            }
        }
        debugOutput("dslExecuteMap-end");

        function execute() {
            $("#result").html("execute...");
            if (urlParam.dsl) {
                dslExecuteMap[urlParam.dsl]();
            } else {
                innerExecute();
            }
        }

        function innerExecute() {

            parseASTAndDo(function(result) {

                var astText = result.ast;
                if (!astText) {
                    alert("执行代码错误: " + JSON.stringify(result));
                    return;
                }

                var dataJson = JSON.parse(astText);

                var code = getCode();

                $("#result").html("");

                //默认场景使用
                var value = executeAST(dataJson.ast);
                if (usePrintlnFlag) {
                    return;
                }

                var resultText = formatValue(value);
                $("#result").append(resultText);
            })
        }

        var usePrintlnFlag = false;

        function executeAST(ast) {

            var input = {};
            var context = {};
            var runtime = new OpenDSLRuntime.default(context);
            runtime.registerSystemFunction({
                println: function(text) {
                    usePrintlnFlag = true;
                    if (undefined === text) {
                        text = "";
                    }
                    var resultText = formatValue(text) + "<br/>";

                    $("#result").append(resultText);

                    return null;
                },
                abs: function(number) {
                    return Math.abs(number);
                },
                math: Math
            });
            runtime.registerCheck(function(output) {
                if (output && "object" === typeof(output) && output.message) {
                    alert(output.message);
                    return false;
                }
                return true;
            })
            runtime.prepare(ast);
            var localContext = {};
            var output = runtime.execute(ast, input, localContext);
            return output;
        }

        function parseASTAndDo(action) {
            var code = getCode();

            if (urlParam.grammar) {
                $.get(urlParam.grammar, function(dsl) {
                    var param = {
                        code: encodeURI(code),
                        dsl: encodeURI(dsl)
                    }
                    $("#executeBtn").attr("disabled", true);
                    $.post("/dsl/parse/to/ast", param, function(result) {
                        $("#executeBtn").attr("disabled", false);
                        if (action) {
                            action(result);
                        }
                    })
                })
            } else {
                alert("缺少语法文件URL参数: &grammar=URL!");
            }
        }

        debugOutput("formatValue-end");

        function formatValue(value) {
            var resultText;
            if (null === value) {
                resultText = "null";
            } else if ("string" === typeof(value)) {
                resultText = value;
            } else if ("object" === typeof(value)) {
                resultText = JSON.stringify(value);
            } else if ("boolean" === typeof(value)) {
                resultText = value;
            } else if (isNaN(value)) {
                resultText = value;
            } else {
                resultText = Math.round(value * 10000000000) / 10000000000;
            }
            return resultText;
        }

        var vimMode = null;
        var isVimMode = false;

        function toggleVimMode() {

            if (MonacoVim) {
                if (isVimMode) {
                    vimMode.dispose();
                    $("#toggleVimModeBtn").text("切换VIM模式");
                    $("#vimStatus").hide();
                } else {
                    $("#toggleVimModeBtn").text("切换普通模式");
                    var vimStatusNode = document.getElementById('vimStatus');
                    vimMode = MonacoVim.initVimMode(dslCodeEditor, vimStatusNode);
                    $("#vimStatus").show();
                }

                isVimMode = !isVimMode;
            }
        }

        var indentTexts = [];

        function getAST(code) {
            return grammarParser.parse(code);
        }

        function formatCode() {
            indentTexts = [];
            var code = getCode();

            var astJson = getAST(code);

            var text = getAllTextFromASTNode(astJson.ast);

            text = text.replace(/\n\s+\n/g, "\n\n");
            text = text.replace(/(([,=] |[\(])[+-]+) (\w+)/g, "$1$3");
            text = text.replace(/\+ ,/g, "+,");

            dslCodeEditor.setValue(text);

            return text;
        }

        var noSpaceTokens = ["(", ")", ";", ".", ",", "}", "[", "]"];
        var noPreSpaceTokens = ["(", "{", ".", '"', "'", "["];
        var nextSpaceTokens = ["if", "=", "+", "-", "*", "/", "%", "**", ">", "<", "<=", ">=", "!=", "==", "=~"];

        var noSpacePairTokens = [
            ["*", ";"]
        ]
        var preText = "";
        var isFirstToken = true;

        function isNoSpacePairTokens(text, preText) {

            for (var i = 0; i < noSpacePairTokens.length; i++) {
                if (noSpacePairTokens[i][0] === preText && noSpacePairTokens[i][1] === text) {
                    return true;
                }
            }
            return false;
        }

        debugOutput("getAllTextFromASTNode-end");

        function getAllTextFromASTNode(node) {
            if (!node) {
                return "";
            }
            var text = "";

            if (node.text && "<EOF>" !== node.text && " " !== node.text) {
                if (node.text === "}" || node.text === "end") {
                    indentTexts.pop();
                }
                if (isFirstToken && indentTexts.length > 0) {
                    text += indentTexts.join("");
                }
                if ("RegularLikeRight" === node.rule) {
                    text += node.text.replace(/=~\s*/, " =~ ");
                } else if (isFirstToken ||
                    isNoSpacePairTokens(node.text, preText) ||
                    (noSpaceTokens.indexOf(node.text) > -1 && nextSpaceTokens.indexOf(preText) === -1) ||
                    noPreSpaceTokens.indexOf(preText) > -1) {
                    text += node.text;
                } else {
                    if (node.column > 1 && node.text !== "\n") {
                        text += " ";
                    }
                    text += node.text;
                }
                preText = node.text;
                if (node.text === "{" || node.text === "->") {
                    indentTexts.push("  ");
                }
                if (node.text === "\n" || "Comment" === node.rule) {
                    isFirstToken = true;
                } else {
                    isFirstToken = false;
                }
            }
            if (node.children) {
                for (var i = 0; i < node.children.length; i++) {
                    text += getAllTextFromASTNode(node.children[i]);
                }
            }
            return text;
        }

        debugOutput("require(['vs/editor/editor.main'-begin");
        require.config({
            paths: {
                vs: 'https://7072-prd-ffx26-1255301037.tcb.qcloud.la/321zou.com/dsl/monaco-editor/0.32.1/min/vs',
                // vs: '/depend/monaco/min/vs',
                'monaco-vim': 'https://unpkg.com/monaco-vim/dist/monaco-vim',
            }
        });

        require(['vs/editor/editor.main'], function() {

            $("#dslCodeInner").hide();

            // Register a new language
            monaco.languages.register({
                id: 'dsl'
            });

            monaco.editor.onDidCreateModel(function(model) {
                function validate() {

                    var textToValidate = model.getValue();
                    if (!textToValidate) {
                        return;
                    }
                    var astJson = grammarParser.parse(textToValidate);
                    var markers = [];

                    for (var i = 0; i < astJson.errors.length; i++) {
                        var error = astJson.errors[i];
                        markers.push({
                            severity: monaco.MarkerSeverity.Error,
                            startLineNumber: error.line,
                            startColumn: error.column,
                            endLineNumber: error.line,
                            endColumn: error.column,
                            message: error.message
                        });
                    }

                    for (var i = 1; i < astJson.ast.children.length - 1; i++) {
                        var child = astJson.ast.children[i];
                        markers.push({
                            severity: monaco.MarkerSeverity.Error,
                            startLineNumber: child.line,
                            startColumn: child.column,
                            endLineNumber: child.line,
                            endColumn: child.column,
                            message: child.text
                        });
                    }

                    monaco.editor.setModelMarkers(model, 'dsl', markers);

                }

                var handle = null;
                model.onDidChangeContent(() => {
                    clearTimeout(handle);
                    handle = setTimeout(() => validate(), 500);
                });
                validate();
            });

            if (codeUrl) {
                $.get(codeUrl, function(dslCode) {

                    dslCodeEditor = monaco.editor.create(document.getElementById('dslCode'), {
                        value: dslCode,
                        language: 'dsl',
                        theme: 'dslTheme',
                        scrollBeyondLastLine: false,
                    });
                    $("#dslCodeInner").hide();

                    var myCondition1 = dslCodeEditor.createContextKey( /*key name*/ 'myCondition1', /*default value*/ true);

                    dslCodeEditor.addCommand(
                        monaco.KeyCode.Enter,
                        function() {
                            dealIndentOnEnter();
                        },
                        'myCondition1'
                    );

                })
            } else {
                dslCodeEditor = monaco.editor.create(document.getElementById('dslCode'), {
                    value: code,
                    language: 'dsl',
                    theme: 'dslTheme',
                    scrollBeyondLastLine: false,
                });
            }

            var monarchUrl = urlParam.monarch || "/git/grammar-repo/opendsl/0.4/monarch.js";
            $.get(monarchUrl, function(js) {
                var hightLight = eval(js);
                monaco.languages.setMonarchTokensProvider('dsl', hightLight);
            })

            var completeUrl = urlParam.complete;
            $.get(completeUrl, function(js) {
                var suggestions = eval(js);

                monaco.languages.registerCompletionItemProvider('dsl', {
                    provideCompletionItems: () => {
                        suggestions.forEach(suggestion => {
                            delete suggestion.range;
                        });
                        return {
                            suggestions
                        };
                    }
                })
            });

            require(['monaco-vim'], function(theMonacoVim) {
                MonacoVim = theMonacoVim;
            });
        });

        //缩进处理
        function dealIndentOnEnter() {

            //获取当前位置
            var line = dslCodeEditor.getPosition();

            //获取当前内容
            var model = dslCodeEditor.getModel();
            var currentText = model.getLineContent(line.lineNumber);

            //获取当前内容缩进的空格数
            var indentSpace = getIndentSpace(currentText);

            var text = "\n" + indentSpace;
            if (isAtBracket(currentText)) {
                text += "  ";
            }
            var range = new monaco.Range(line.lineNumber, line.column, line.lineNumber, line.column);
            var id = {
                major: 1,
                minor: 1
            };
            var op = {
                identifier: id,
                range: range,
                text: text,
                forceMoveMarkers: true
            };
            dslCodeEditor.executeEdits("my-source", [op]);
        }
        if (GrammarParser) {
            grammarParser = new GrammarParser.default();
        }
        debugOutput("require(['vs/editor/editor.main'-end");
    </script>
</body>

</html>

<!--对外 API 部分-->
<script>
    debugOutput("API-begin");
    if (parent && parent.bindDSLEditorAPI) {
        parent.bindDSLEditorAPI({
            getCode: function() {
                return getCode();
            },
            setCode: function(code) {
                setCode(code);
            },
            getAST: function() {
                return getAST(getCode());
            },
            executeCode: function(callback) {
                dslExecuteMap[urlParam.dsl](callback);
            },
            formatCode: function() {
                formatCode();
            },
            getEditMode: function() {
                return isVimMode ? "vim" : "default";
            },
            setEditMode: function(mode) {
                if ("vim" === mode && !isVimMode) {
                    toggleVimMode();
                } else if ("default" === mode && isVimMode) {
                    toggleVimMode();
                }
            },
        });
    }
    debugOutput("API-end");
</script>

<script>
    var _hmt = _hmt || [];
    (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?a44a91faad51b8b8a6abefeedf894dac";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>